package com.ds.lens.data.service;

import com.github.pagehelper.Page;
import com.ds.lens.data.common.asserts.Asserts;
import com.ds.lens.data.common.constant.Constants;
import com.ds.lens.data.common.document.MailConfig;
import com.ds.lens.data.common.util.MD5Util;
import com.ds.lens.data.common.util.MailUtil;
import com.ds.lens.data.security.RedisClient;
import com.ds.lens.data.service.dao.LensApplicationDao;
import com.ds.lens.data.service.dao.LensRoleDao;
import com.ds.lens.data.service.dao.LensUserDao;
import com.ds.lens.data.service.dao.LensUserRoleRelationDao;
import com.ds.lens.data.service.dao.entity.LensApplication;
import com.ds.lens.data.service.dao.entity.LensRole;
import com.ds.lens.data.service.dao.entity.LensUser;
import com.ds.lens.data.service.dao.entity.LensUserRoleRelation;
import com.ds.lens.data.vo.in.PagedConditionInVO;
import com.ds.lens.data.vo.in.v2.user.LensUserInVO;
import com.ds.lens.data.vo.in.v2.user.LensUserSearchInVO;
import com.ds.lens.data.vo.out.PageResult;
import com.ds.lens.data.vo.out.v2.lensuser.LensUserOutVO;
import com.ds.lens.data.vo.out.v2.lensuser.UserProfileOutVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.mail.EmailException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.ds.lens.data.common.constant.Constants.REDIS_KEY_FORGET_PASSWORD;

/**
 * Description:
 *
 * @author WeiShaoying
 * @date 2019-12-03
 */

@Slf4j
@Service
public class LensUserService extends BaseService {

    private static final String EMAIL_REGEX = "[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+";
    /**
     * 忘记密码token失效时间 timeout单位为分
     * 默认30分钟
     */
    @Value("${lens.forget.password.token.timeout:30}")
    private long forgetPasswordTokenTimeout;

    @Value("${lens.security.service.name}")
    private String webSiteHost;

    @Autowired
    LensUserDao lensUserDao;

    @Autowired
    LensUserRoleRelationDao lensUserRoleRelationDao;

    @Autowired
    LensRoleDao lensRoleDao;

    @Autowired
    private RedisClient redisClient;

    @Autowired
    MailConfig mailConfig;

    @Autowired
    LensApplicationDao lensApplicationDao;

    public void changePassword(long lensUserId, String oldPassword, String newPassword) {
        Asserts.assertNotEmpty(newPassword, Constants.ErrorCode.PARAM_ERROR, "newPassword can not be empty.");
        Asserts.assertNotEmpty(oldPassword, Constants.ErrorCode.PARAM_ERROR, "oldPassword can not be empty.");
        Asserts.assertFalse(oldPassword.equals(newPassword), Constants.ErrorCode.PARAM_ERROR, "The new password cannot be the same as the original password.");
        LensUser lensUser = lensUserDao.selectByPrimaryKey(lensUserId);
        Asserts.assertNotNull(lensUser, Constants.ErrorCode.NO_LENS_USER);
        Asserts.assertTrue(lensUser.getPassword().equals(MD5Util.getMD5(oldPassword)), Constants.ErrorCode.WRONG_PASSWORD);
        // Change password.
        Asserts.assertTrue(lensUserDao.updatePasswordByPrimaryKey(MD5Util.getMD5(newPassword), lensUser.getId()) > 0,
                Constants.ErrorCode.FAILED_TO_RESET_PASSWORD);
    }

    public UserProfileOutVO userProfile(Long uid, int userType, boolean isAdmin) {
        LensUser lensUser = lensUserDao.selectByPrimaryKey(uid);
        Asserts.assertNotNull(lensUser, Constants.ErrorCode.NO_LENS_USER);
        UserProfileOutVO outVO = new UserProfileOutVO();
        List<LensApplication> adminApps = lensApplicationDao.queryAllLensApplications(lensUser.getName(), userType, Constants.ApplicationRoleEnum.APPLICATION_ADMIN.getCode());
        outVO.setEmail(lensUser.getEmail());
        outVO.setUserName(lensUser.getName());
        if (CollectionUtils.isNotEmpty(adminApps)) {
            outVO.setOwnerApps(adminApps.stream().map(LensApplication::getName).collect(Collectors.toList()));
        } else {
            outVO.setOwnerApps(Collections.emptyList());
        }
        List<LensApplication> userApps = lensApplicationDao.queryAllLensApplications(lensUser.getName(), userType, Constants.ApplicationRoleEnum.APPLICATION_USER.getCode());
        if (CollectionUtils.isNotEmpty(userApps)) {
            outVO.setUserApps(userApps.stream().map(LensApplication::getName).collect(Collectors.toList()));
        } else {
            outVO.setUserApps(Collections.emptyList());
        }
        outVO.setIsAdmin(isAdmin);
        return outVO;
    }

    public PageResult<LensUserOutVO> findLensUsers(PagedConditionInVO<LensUserSearchInVO> searchVO) {
        Page<LensUser> page = lensUserDao.selectLensUsers(getPageCondition(searchVO));
        if (CollectionUtils.isEmpty(page.getResult())) {
            return PageResult.of(Collections.emptyList(), page.getTotal(), page.getPageNum(), page.getPageSize());
        }
        List<LensUserOutVO> result = page.getResult().stream().map(this::getLensUserOutVO).collect(Collectors.toList());
        return PageResult.of(result, page.getTotal(), page.getPageNum(), page.getPageSize());
    }

    private LensUserOutVO getLensUserOutVO(LensUser lensUser) {
        LensUserOutVO outVO = new LensUserOutVO();
        outVO.setId(lensUser.getId());
        outVO.setName(lensUser.getName());
        outVO.setEmail(lensUser.getEmail());
        outVO.setCreatedTime(lensUser.getCreatedTime().getTime());
        return outVO;
    }

    public List<String> findAllLensUserNames() {

        List<LensUser> lensUsers = lensUserDao.selectAllLensUsers();
        if (CollectionUtils.isNotEmpty(lensUsers)) {
            return lensUsers.stream().map(LensUser::getName).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    public List<LensUserOutVO> findAllLensUsers() {
        List<LensUser> lensUsers = lensUserDao.selectAllLensUsers();
        if (CollectionUtils.isNotEmpty(lensUsers)) {
            return lensUsers.stream().map(this::getLensUserOutVO).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    public void resetPassword(String newPassword, String token) {
        Asserts.assertNotEmpty(newPassword, Constants.ErrorCode.PARAM_ERROR, "newPassword can not be empty.");
        Asserts.assertNotEmpty(token, Constants.ErrorCode.PARAM_ERROR, "token can not be empty.");
        // Check if the token has expired.
        String lensUserId = redisClient.getString(REDIS_KEY_FORGET_PASSWORD + token);
        Asserts.assertNotEmpty(lensUserId, Constants.ErrorCode.TOKEN_INVALIDATION);
        LensUser lensUser = lensUserDao.selectByPrimaryKey(Long.parseLong(lensUserId));
        Asserts.assertNotNull(lensUser, Constants.ErrorCode.NO_LENS_USER);
        // Reset password.
        Asserts.assertTrue(lensUserDao.updatePasswordByPrimaryKey(MD5Util.getMD5(newPassword), lensUser.getId()) > 0,
                Constants.ErrorCode.FAILED_TO_RESET_PASSWORD);
        // Delete token cache.
        redisClient.del(REDIS_KEY_FORGET_PASSWORD + token);
    }

    public void forgetPassword(String param) {
        Asserts.assertNotEmpty(param, Constants.ErrorCode.PARAM_ERROR, "Email can not be empty.");
        LensUser lensUser;
        // Check if the param is email.
        if (checkEmailFormat(param)) {
            // Check if email is valid.
            lensUser = lensUserDao.selectLensUserByEmail(param);
        } else {
            lensUser = lensUserDao.selectLensUserByUserName(param);
        }
        Asserts.assertNotNull(lensUser, Constants.ErrorCode.NO_LENS_USER);
        // Generate token and put it into cache and set expiration time.
        String token = java.util.UUID.randomUUID().toString();
        redisClient.setWithExpiration(REDIS_KEY_FORGET_PASSWORD + token, String.valueOf(lensUser.getId()), forgetPasswordTokenTimeout, TimeUnit.MINUTES);
        // Send email.
        StringBuilder content = new StringBuilder("您好：\n");
        content.append("    请点击此链接重新设置您的密码:<a href=\"http://");
        content.append(webSiteHost);
        content.append("/reset-password?key=").append(token);
        content.append("\">重置密码</a>");
        content.append("\n 提示：该链接将在").append(forgetPasswordTokenTimeout).append("分钟后失效。");
        String subject = "Lens系统重置密码";
        try {
            MailUtil.sendHTMLMail(mailConfig, content.toString(), subject, lensUser.getEmail());
        } catch (EmailException e) {
            log.error("Failed to send email.", e);
        }
    }

    private boolean checkEmailFormat(String email) {
        boolean tag = true;
        if (!email.matches(EMAIL_REGEX)) {
            tag = false;
        }
        return tag;
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Throwable.class)
    public void lensRegister(LensUserInVO inVO) {
        Asserts.assertNotNull(inVO, Constants.ErrorCode.PARAM_ERROR, "Registration parameter cannot be empty");
        // insert lens user
        LensUser lensUser = insertLensUser(inVO.getUserName(), inVO.getPassword(), inVO.getEmail());
        // insert user role relation
        if (!lensUserRoleRelationDao.relationExisted(lensUser.getName(), null)) {
            LensRole lensRole = lensRoleDao.queryRoleByName(Constants.RoleEnum.USER.getName());
            LensUserRoleRelation relation = new LensUserRoleRelation();
            relation.setRoleId(lensRole.getId());
            relation.setUserName(lensUser.getName());
            Asserts.assertTrue(lensUserRoleRelationDao.insert(relation) > 0, Constants.ErrorCode.INSERT_LENS_USER_FAIL);
        }
    }

    public boolean emailExisted(String email) {
        Asserts.assertNotEmpty(email, Constants.ErrorCode.PARAM_ERROR, "email can not be empty.");

        return lensUserDao.emailExisted(email);
    }

    public boolean userNameExisted(String userName) {
        Asserts.assertNotEmpty(userName, Constants.ErrorCode.PARAM_ERROR, "userName can not be empty.");

        return lensUserDao.userNameExisted(userName);
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Throwable.class)
    public LensUser insertLensUser(String userName, String password, String email) {
        Asserts.assertNotEmpty(userName, Constants.ErrorCode.PARAM_ERROR, "userName can not be empty.");
        Asserts.assertNotEmpty(password, Constants.ErrorCode.PARAM_ERROR, "password can not be empty.");
        Asserts.assertNotEmpty(email, Constants.ErrorCode.PARAM_ERROR, "email can not be empty.");
        Asserts.assertFalse(userNameExisted(userName), Constants.ErrorCode.USERNAME_EXIST);
        Asserts.assertFalse(emailExisted(email), Constants.ErrorCode.EMAIL_EXIST);
        LensUser lensUser = new LensUser();
        lensUser.setName(userName);
        lensUser.setPassword(MD5Util.getMD5(password));
        lensUser.setEmail(email);
        Date date = new Date();
        lensUser.setCreatedTime(date);
        lensUser.setModifiedTime(date);
        Asserts.assertTrue(lensUserDao.insert(lensUser) > 0, Constants.ErrorCode.INSERT_LENS_USER_FAIL);
        return lensUser;
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Throwable.class)
    public boolean updateLensUser(LensUser lensUser) {
        Asserts.assertNotNull(lensUser, Constants.ErrorCode.PARAM_ERROR, "参数不能为空。");
        Asserts.assertNotNull(lensUser.getId(), Constants.ErrorCode.PARAM_ERROR, "ID不能为空。");
        if (StringUtils.isNotEmpty(lensUser.getName())) {
            Asserts.assertFalse(lensUserDao.userNameExisted(lensUser.getName()), Constants.ErrorCode.PARAM_ERROR, "用户名已存在。");
        }
        return lensUserDao.updateByPrimaryKeySelective(lensUser) > 0;
    }

    public LensUser getLensUserById(Long id) {
        Asserts.assertNotNull(id, Constants.ErrorCode.PARAM_ERROR, "ID不能为空。");
        return lensUserDao.selectByPrimaryKey(id);
    }

    public PageResult<LensUser> findLensUsersByPage(PagedConditionInVO<LensUser> conditionInVO) {

        Page<LensUser> page = lensUserDao.selectByCondition(getPageCondition(conditionInVO));
        if (CollectionUtils.isEmpty(page.getResult())) {
            return PageResult.of(Collections.emptyList(), 0L, page.getPageNum(), page.getPageSize());
        }

        return PageResult.of(page.getResult(), page.getTotal(), page.getPageNum(), page.getPageSize());
    }
}
